//*********************************************************************
//		Copyright (c) 2003 - 2008 Microsoft Corporation.
//      This code is provided under Microsoft Public License, which can be found at
//      http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx 
//
// @File: H2Manager.cs
//
// Purpose: OBject running collection (launching Collectors)
//
//
//
// Notes: 
//
//
//	
// History: 
//     @Version: V2.1
//
//     07/14/08 PGV 2.1.001.0 Starting V2.1 (based on 2.0.030.0 code)
//
//
// @EndHeader@
//*********************************************************************

using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using Microsoft.Win32;
using System.Diagnostics;

using H2V2.Collector;

namespace H2V2
{
	public class H2Manager
	{
		public uint m_options = CmdLineArg.ManagerOptions; 
		// 0x1 - Targets; 0x2 - PerfProviders; 0x4 - Exporter

		EventLog eLog;

		static LogFile ms_log;
		public static LogFile Log { get {return ms_log;} }
		public string version { get { return Constants.Version; } }

		static string m_repository_instance; 
		static Connection ms_repository; 
		public static Connection Repository { get {return ms_repository;} }

		static int m_host_id;
		public static int HostID { get {return m_host_id;} }
		static int m_run_id;
		public static int RunID { get {return m_run_id;} }
		static Guid m_db_guid;
		public static Guid dbGuid { get {return m_db_guid;} }

		public static int TrackID = 0;

		#region Status 
		// This function is to be delegated to Targets
		void OnStatusChange (object source, StatusChangeEventArgs args)
		{
			if (args.m_type != StatusEventType.EXCEPTION)
			{
				ChangeStatus (args.m_text, args.m_type);
			}
			else
			{
				if (args.m_exception.GetType ().ToString () == "SqlException")
				{
					Log.SQLException ((SqlException)args.m_exception);
				}
				else
				{
					ChangeStatus (args.m_exception.Message, StatusEventType.ERROR);
				}
			}
		}

		void ChangeStatus (string a_msg)
		{
			ChangeStatus (a_msg, StatusEventType.STATUS);
		}

		void ChangeStatus (string a_msg, StatusEventType a_type)
		{
			string setype="",
				msg;

			switch (a_type)
			{
				case StatusEventType.NOTHING:
					setype = "\t";
					break;
				case StatusEventType.STATUS:
					setype = "Status: \t";
					break;
				case StatusEventType.ERROR:
					setype = "ERROR: \t";
					break;
				case StatusEventType.INFO:
					setype = "INFO: \t";
					break;
				case StatusEventType.WARNING:
					setype = "WARNING: \t";
					break;
				case StatusEventType.DEBUG:
					Log.WriteDebug (a_msg);
					return;
				case StatusEventType.STACK_TRACE:
					Log.WriteStack (a_msg);
					return;
			}

			msg = setype+a_msg;
			Log.Write (msg);
		}

		#endregion

		public H2Manager()
		{
			eLog = new EventLog ("Application", ".", "SQLH2");
			eLog.WriteEntry ("Starting SQLH2 ver."+version, EventLogEntryType.Information, 1); 

			ms_log = new LogFile (true);
			ChangeStatus ("SQLH2  ver. "+version, StatusEventType.NOTHING);
			LogOptions ();
		}

		void LogOptions ()
		{
			string s = "";

			if (CmdLineArg.Args.Length > 0)
			{
				foreach (string a in CmdLineArg.Args)
				{
					s += " "+a;
				}
				ChangeStatus ("The following command line arguments were supplied:"+s, StatusEventType.INFO);
			}
			if (m_options != 0xffff)
			{
				ChangeStatus ("H2 started with options bitmask 0x"+m_options.ToString ("x4"), StatusEventType.INFO);
				if ((m_options & 0x1) == 0x1) ChangeStatus (" 0x01 - Targets processing", StatusEventType.INFO);
				if ((m_options & 0x2) == 0x2) ChangeStatus (" 0x02 - PerfProviders processing", StatusEventType.INFO);
				if ((m_options & 0x4) == 0x4) ChangeStatus (" 0x04 - Exporter processing", StatusEventType.INFO);
			}
		}

		public bool Init (string user, string pwd)
		{
			ChangeStatus ("Init");

			Configurator cfg = new Configurator (); 
			try
			{
				cfg.ReadConfig ();
			}
			catch (System.IO.FileNotFoundException)
			{
				ChangeStatus ("Cannot find configuration file "+CmdLineArg.ConfigFile, StatusEventType.ERROR);
				ChangeStatus ("Use /C key to point to a file location", StatusEventType.NOTHING);
				ChangeStatus ("H2 shuts down", StatusEventType.NOTHING);
				return false;
			}
			catch (ConfigException cex)
			{
				ChangeStatus ("Errors encountered processing "+CmdLineArg.ConfigFile, StatusEventType.ERROR);
				ChangeStatus (cex.Message, StatusEventType.ERROR);
				ChangeStatus ("Correct the problem in configuration file and rerun the program", StatusEventType.NOTHING);
				ChangeStatus ("H2 shuts down", StatusEventType.NOTHING);
				return false;
			}
			catch (Exception ex)
			{
				ChangeStatus ("Errors encountered processing "+CmdLineArg.ConfigFile, StatusEventType.ERROR);
				ChangeStatus (ex.Message, StatusEventType.ERROR);
				ChangeStatus ("H2 shuts down", StatusEventType.NOTHING);
				return false;
			}

			if (Configurator.Computers_array.Count == 0 && (m_options & 0x01) == 0x01)
			{
				ChangeStatus ("There are no Targets in the config file", StatusEventType.WARNING);
			}
			if (Configurator.Perf_array.Count == 0 && (m_options & 0x02) == 0x02)
			{
				ChangeStatus ("There are no PerfProviders in the config file", StatusEventType.WARNING);
			}

			try 
			{
				Init_repository (user, pwd);
			}
			catch (Exception ex)
			{
				if (ex.Message.StartsWith ("Current H2 version requeries Repository of linear version"))
				{
					ChangeStatus (ex.Message, StatusEventType.ERROR);
				}
				else
				{
					ChangeStatus (ex.Message, StatusEventType.ERROR);
					ChangeStatus ("Repository connection initialization failed", StatusEventType.ERROR);
				}
				ChangeStatus ("H2 shuts down", StatusEventType.NOTHING);
				return false;
			}

			return true;
		}

		void Init_repository (string user, string pwd)
		{
			m_repository_instance = Constants.RepositoryServer;


			ChangeStatus (" Initializing repository: "+m_repository_instance); 

			ms_repository = new Connection (ConnectionType.REPOSITORY); 
			ms_repository.OnStatusChangeHandler += new Connection.StatusChangeEventHandler(OnStatusChange);
			if (null == user)
			{
				ms_repository.Init (m_repository_instance, null);
			}
			else
			{
				string connectionString =
					String.Format("server={0};User ID={1};Password={2}",m_repository_instance,user,pwd);
				ms_repository.Init (m_repository_instance, connectionString);
			}


			ChangeStatus (" Repository Database: "+Constants.RepositoryDB);
			ms_repository.SQLConnection.ChangeDatabase (Constants.RepositoryDB);
            SetQuotedRepositoryName ();

			if (!CheckRepositoryVersion ())
			{
				ms_repository.Close ();
				throw new Exception ("Current H2 version requeries Repository of linear version "+Constants.REPOSITORY_VERSION);
			}

			string s_guid = (string)ms_repository.ExecuteScalar ("Select cvalue From dbo.sys_config Where config=0");
			m_db_guid = new Guid (s_guid);

			ms_repository.StoreNonQuery ("SET QUOTED_IDENTIFIER OFF");
			
			if (CmdLineArg.bCustomDateFormat)
			{
				ms_repository.StoreNonQuery ("SET DATEFORMAT "+CmdLineArg.CustomDateFormat);
			}

			RegisterHost ();
		}

		bool CheckRepositoryVersion ()
		{
			int iver = 0;

			try
			{
				iver = (int)ms_repository.ExecuteScalar ("Select ivalue From dbo.sys_config Where config=2");
				Constants.RepositoryVersion = iver;
			}
			catch (NullReferenceException)
			{
				return false;
			}

			return (iver >= Constants.REPOSITORY_VERSION);
		}

        void SetQuotedRepositoryName ()
        {
            SqlCommand sqlcmd = new SqlCommand ();
            sqlcmd.Connection = ms_repository.SQLConnection;
            sqlcmd.CommandText = "SELECT QUOTENAME(@name)";
            sqlcmd.Parameters.AddWithValue ("@name", Constants.RepositoryDB);

            Constants.QuotedRepositoryDB = (string)sqlcmd.ExecuteScalar ();
        }

		public void Close ()
		{
			ChangeStatus ("SQLH2 is shutting down", StatusEventType.NOTHING);
			Log.Close ();
			eLog.WriteEntry ("SQLH2 stopped", EventLogEntryType.Information, 2); 
		}

		void RegisterHost ()
		{
			SqlCommand rcmd;
			SqlParameter p_name,
				p_host_id,
				retval;
			int retcode;


			rcmd = new SqlCommand ("dbo.spr_host", ms_repository.SQLConnection);
			rcmd.CommandType = CommandType.StoredProcedure;

			retval = new SqlParameter ("retvalue", SqlDbType.Int);
			retval.Direction = ParameterDirection.ReturnValue;
			rcmd.Parameters.Add (retval);
			p_name = new SqlParameter ("@name", SqlDbType.NVarChar);
			p_name.Direction = ParameterDirection.Input;
			p_name.Value = Environment.MachineName;
			rcmd.Parameters.Add (p_name);
			p_host_id = new SqlParameter ("@host_id", SqlDbType.Int);
			p_host_id.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (p_host_id);

			rcmd.ExecuteNonQuery ();
			try 
			{
				retcode = (int)retval.Value;
				m_host_id = (int)p_host_id.Value;
				ChangeStatus ("host_id = "+m_host_id, StatusEventType.INFO);
			}
			catch (SqlException sqlex)
			{
				Log.SQLException (sqlex);
				ChangeStatus (sqlex.StackTrace, StatusEventType.STACK_TRACE);
				throw (new StorageErrorException (CollectorError.StorSyntaxErrorReaderSP,sqlex.Number,"dbo.spr_host"));
			}
		}
        
		void RegisterRun ()
		{
			SqlCommand rcmd;
			SqlParameter param;
			SqlParameter retval;

			ChangeStatus ("Registering Run");

			// register run and obtain run_id
			rcmd = new SqlCommand ("dbo.spr_run", ms_repository.SQLConnection);
			rcmd.CommandType = CommandType.StoredProcedure;

			retval = new SqlParameter ("retvalue", SqlDbType.Int);
			retval.Direction = ParameterDirection.ReturnValue;
			rcmd.Parameters.Add (retval);
			param = new SqlParameter ("@run_id", SqlDbType.Int);
			param.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (param);
            rcmd.Parameters.AddWithValue ("@host_id", m_host_id);
            rcmd.Parameters.AddWithValue ("@srv_id", 0); // always 0 as run is no longer associated with a single server
            rcmd.Parameters.AddWithValue ("@options", (int)m_options);

            string comment = Constants.Version;
			foreach (string s in CmdLineArg.Args)
			{
				comment += " "+s;
			}
            rcmd.Parameters.AddWithValue ("@comment", comment);

			try 
			{
                rcmd.ExecuteNonQuery ();
                m_run_id = (int)param.Value;
				ChangeStatus ("run_id = "+m_run_id, StatusEventType.INFO);
			}
			catch (SqlException sqlex)
			{
				Log.SQLException (sqlex);
				ChangeStatus (sqlex.StackTrace, StatusEventType.STACK_TRACE);
				throw (new StorageErrorException (CollectorError.StorSyntaxErrorReaderSP,sqlex.Number,"dbo.spr_run"));
			}
		}

		public void Run ()
		{
			int q_errors = 0,
				q = 0;
			short state = 0;

			ChangeStatus ("Starting RUN  ");
			RegisterRun ();

			if ((m_options & 0x1) == 1)
			{
				state = 1;
				ChangeStatus ("Starting Targets processing  ");
				foreach (Computer s_comp in Configurator.Computers_array)
				{
					try
					{
						Target t = new Target (s_comp.Name, s_comp.ScanDrives);
                        
						t.OnStatusChangeHandler += new Target.StatusChangeEventHandler(OnStatusChange);

						if (CmdLineArg.bDebugTargetOptions) t.Options = CmdLineArg.DebugTargetOptions;

						t.Init ();
						t.Run ();
						++q;
					}
					catch (Exception ex)
					{
						if (GlobalEnvironment.RepositoryFailure)
						{
							ChangeStatus ("Critical Repository Failure", StatusEventType.ERROR);
							Close ();
							throw;
						}
						ChangeStatus (ex.Message, StatusEventType.ERROR);
						++q_errors;
					}
				}

				if (q > 0) UpdateRunState (state);
				if (q_errors > 0) ChangeStatus ("Errors were encountered processing Targets", StatusEventType.WARNING);
				ChangeStatus ("Targets processing finished");
			}


			if ((m_options & 0x2) == 2)
			{
				state |= 0x2;
				q = 0;
				q_errors = 0;
				ChangeStatus ("Starting Perf Loader");
				foreach (string s_pp in Configurator.Perf_array)
				{
					try
					{
						PerfProvider pp = new PerfProvider (s_pp);
						pp.OnStatusChangeHandler += new PerfProvider.StatusChangeEventHandler(OnStatusChange);
						pp.Init ();
						pp.Load ();
						++q;
					}
					catch (Exception ex)
					{
						ChangeStatus (ex.Message, StatusEventType.ERROR);
						++q_errors;
					}
				}
				if (q > 0) UpdateRunState (state);
				if (q_errors > 0) ChangeStatus ("Errors were encountered processing PerfProviders", StatusEventType.WARNING);
				ChangeStatus ("Perf Loader finished ");
			}

			if ((m_options & 0x4) == 4)
			{ 
				state |= 0x4;

				if (CmdLineArg.bExplicitOptions ||
					(!CmdLineArg.bExplicitOptions && Constants.ExportData))
				{
					try
					{
						ChangeStatus ("Starting Data Exporter ");
						if (Exporter.CheckPermissions ())
						{
							Exporter e = new Exporter ();
							e.OnStatusChangeHandler += new Exporter.StatusChangeEventHandler(OnStatusChange);
							e.Init ();
							e.Run ();
						}
						else
						{
							ChangeStatus ("Data Export is not allowed for this Repository");
						}
						ChangeStatus ("Data Exporter finished ");
						UpdateRunState (state);
					}
					catch (Exception ex)
					{
						ChangeStatus (ex.StackTrace, StatusEventType.STACK_TRACE);
						ChangeStatus ("Data Exporter failed ", StatusEventType.WARNING);
					}
				}
			}
		}

		public static void UpdateRunState (short a_State)
		{
            SqlCommand sqlcmd = new SqlCommand ("Update dbo.mgr_run Set state=@state Where run_id=@run_id ",
                ms_repository.SQLConnection);
            sqlcmd.Parameters.AddWithValue ("@state", a_State);
            sqlcmd.Parameters.AddWithValue ("@run_id", m_run_id);

            sqlcmd.ExecuteNonQuery ();
		}

		public static void LogMgrError 
			(int srv_id, short inst_id, RunErrorClass class_id, int obj_id, string obj_name, 
			CollectorError error_id, int aux_error_id, string a_msg)
		{
            SqlCommand sqlcmd = new SqlCommand (
@"Insert dbo.mgr_run_error (run_id,srv_id,inst_id,class_id,track_id,obj_id,obj_name,error_id,aux_error_id,msg) 
Values (@run_id,@srv_id,@inst_id,@class_id,@track_id,@obj_id,@obj_name,@error_id,@aux_error_id,@msg) ",
                ms_repository.SQLConnection);
            sqlcmd.Parameters.AddWithValue ("@run_id", m_run_id);
            sqlcmd.Parameters.AddWithValue ("@srv_id", srv_id);
            sqlcmd.Parameters.AddWithValue ("@inst_id", inst_id);
            sqlcmd.Parameters.AddWithValue ("@class_id", class_id);
            sqlcmd.Parameters.AddWithValue ("@track_id", TrackID);
            sqlcmd.Parameters.AddWithValue ("@obj_id", obj_id);
            sqlcmd.Parameters.AddWithValue ("@obj_name", obj_name);
            sqlcmd.Parameters.AddWithValue ("@error_id", (int)error_id);
            sqlcmd.Parameters.AddWithValue ("@aux_error_id", aux_error_id);
            sqlcmd.Parameters.AddWithValue ("@msg", a_msg);

            sqlcmd.ExecuteNonQuery ();  // direct call outside of Unit (transactional) context
		}
	}
}
